#    Copyright 2011 Ragnvald Fuglestein
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.

from json import dumps as jsondumps
from json import loads as jsonloads
from pickle import dump as pickledump
from pickle import load as pickleload
from base64 import b64encode
from shutil import rmtree
from pyinotify import IN_CLOSE_WRITE
from pyinotify import IN_CREATE
from pyinotify import IN_MOVED_FROM
from pyinotify import IN_MOVED_TO
from pyinotify import IN_DELETE
from pyinotify import WatchManager
from pyinotify import Notifier
from os import path
from os import umask
from os import makedirs
from os import link
from os import remove
from os import stat
from urllib.request import Request
from urllib.request import urlopen
from urllib import error as urlerror
from fnmatch import fnmatch
from shlex import split
from copy import deepcopy
from threading import Thread
from time import sleep
from time import time

g_Sections={}
g_Scripts={}
g_Torrents={}
g_Pickle="Pickle"
g_PathName=0
g_WatchManager=0
g_WatchFlags=IN_CLOSE_WRITE|IN_CREATE|IN_MOVED_FROM|IN_MOVED_TO|IN_DELETE
g_Threads=[]
class Torrent:
   def __init__(self,section=False,filename=False,pickuppath=False,metainfo=False):
      self.section = section
      self.filename = filename
      self.pickuppath = pickuppath
      self.metainfo = metainfo
      self.name = False
      self.uniqueid = False
      self.files = False
      self.frozen = True
      self.hasthread = False
      self.variables = {'downloaddir':False,'torrentcheckinterval':120,'error':''}

   def info(self):
      #print("section=%s\nfilename=%s\npickuppath=%s\nname=%s\nuniqueid=%s\nvariables:"%(self.section,self.filename,self.pickuppath,self.name,self.uniqueid))
#      for varname in self.variables:
#         print("%s = %s"%(varname,self.variables[varname]))
#      print("files:")
#      for i in [listitem for listitem in self.files]:
#         PrintDict(i,['bytes','bytesCompleted'],1)
      for tItem in self.files:
         for tName in tItem.keys():
            tDict=tItem[tName]
            if not 'download' in tDict:
               print("Skipfile:%s"%(tName))

   def complete(self):
      UnfinishedCount=0
      for tItem in self.files:
         for tName in tItem.keys():
            tDict=tItem[tName]
            if not 'download' in tDict:
               continue
            if tDict['bytes']!=tDict['bytesCompleted']:
               UnfinishedCount=UnfinishedCount+1
      if not UnfinishedCount:
         return True
      return False

   def needlink(self):
      for File in self.files:
         for Key in File.keys():
            if 'link' in File[Key]:
               return True
      return False

   def ratioreached(self):
      if not 'maxratio' in self.variables:
         return True
      elif not 'ratio' in self.variables:
         return False
      try:
         if self.variables['ratio']>=self.variables['maxratio']:
            return True
      except TypeError:
         self.variables['maxratio']=float(self.variables['maxratio'])
         self.variables['ratio']=float(self.variables['ratio'])
      return False

class ThreadManager(Thread):
   def __init__(self):
      Thread.__init__(self)
      self.Active=True
      self.Sleep=4
   
   def run(self):
      global g_Threads
      if type(self.type) is Notifier:
         while self.Active:
            self.type.process_events()
            if self.type.check_events(self.Sleep):
               self.type.read_events()
         g_Threads.remove(self)
      elif type(self.type)==type(Torrent()):
         global g_Torrents
         try:
            self.type.variables['nextcheck']=time()+self.type.variables['torrentcheckinterval']
         except TypeError:
            self.type.variables['torrentcheckinterval']=float(self.type.variables['torrentcheckinterval'])
            self.type.variables['nextcheck']=time()+self.type.variables['torrentcheckinterval']
         while self.Active:
            if not self.type.frozen:
               CurrentTime=time()
               if self.type.variables['nextcheck']<CurrentTime:
                  ret=self.MonitorTorrent(self.type)
                  if ret==False:
                     if self.type.uniqueid not in g_Torrents:
                        break
                     self.type.variables['nextcheck']=CurrentTime+self.type.variables['torrentcheckinterval']
                  elif ret==True:
                     self.type.variables['nextcheck']=CurrentTime+self.type.variables['torrentcheckinterval']
            sleep(self.Sleep)
         try:
            print("tm.torrent: self.type.name=%s\nself.type:%s"%(self.type.name,repr(self.type)))
         except:
            pass
         g_Threads.remove(self)

   def MonitorTorrent(self,torrent):
      if not torrent.complete():
         if not UpdateTorrentCompletion(torrent):
            return False
         elif torrent.complete():
            if not Event_TorrentComplete(torrent):
               return False
      if torrent.complete():
         if not torrent.ratioreached():
            if not UpdateRatio(torrent):
               return False
            if torrent.ratioreached():
               if not Event_RatioReached(torrent):
                  return False
         if torrent.ratioreached():
            if not torrent.needlink():
               return Event_TorrentFinished(torrent)
         if not torrent.needlink(): #Fixme!
            torrent.variables['nextcheck']=time()+torrent.variables['torrentcheckinterval']*2
            return 0
         else:
            if not LinkFiles(torrent):
               return False
      return True

def ReadConfig(ConfigFile):
   global g_Sections, g_Scripts, g_Pickle, g_WatchManager
   Dict=None
   Section=False
   try:
      with open(ConfigFile,encoding='utf-8',mode='r') as f:
         for Line in f:
            Option=Line.rstrip()
            if Option and not Option.startswith("#"):
               if Option.startswith("section"):
                  Dict=g_Sections[Option.split(" ",1)[1]]={}
                  Section=True
               elif Option.startswith("script"):
                  Dict=g_Scripts[Option.split(" ",1)[1]]=[]
                  Section=False
               elif Option.startswith("pickle") and Dict==None:
                  g_Pickle=Option.split("=",1)[1]
               else:
                  if Section:
                     key,val=Option.split("=",2)
                     if key=="watchdir":
                        Dict[key]=val
                  else:
                     Dict.append(Option)
   except IOError as e:
      print("Error reading config %s\n%s"%(ConfigFile,e))
      return False
   return True

def UpdateRatio(torrent):
   ratio=False
   if 'transmission' in torrent.variables:
      ratio=TransmissionRpc('torrent-get',{'ids': torrent.uniqueid, 'fields': ['uploadRatio']},torrent)
      if ratio!=False:
         try:
            ratio=ratio['arguments']['torrents'][0]['uploadRatio']
         except IndexError as e:
            print("UpdateRatio: Error getting ratio (%s) Removing"%(e))
            return Event_RemoveTorrent(torrent)
   if ratio!=False:
      torrent.variables['ratio']=float(ratio)

def UpdateTorrentCompletion(torrent):
   files=False
   if 'transmission' in torrent.variables:
      files=TransmissionGetFiles(torrent)
   if files:
      for FileNum,ListItem in enumerate(files):
         for FileName in ListItem.keys():
            Dict=ListItem[FileName]
            bytesCompleted=Dict['bytesCompleted']
            if torrent.files[FileNum][FileName]['bytesCompleted'] != bytesCompleted:
               if not path.isfile("%s/%s"%(torrent.variables['downloaddir'],FileName)):
                  continue
               torrent.files[FileNum][FileName]['bytesCompleted'] = bytesCompleted
               bytes=Dict['bytes']
               if bytes==bytesCompleted:
                  if not Event_FileComplete(torrent,FileName):
                     return False
      return True
   return False

def Event_WatchDir(File):
   global g_Sections,g_Torrents,g_WatchManager,g_PathName
   PathName=File.pathname
   Path=File.path
   Mask=File.mask
   WD=File.wd
   for sectionname in g_Sections.keys():
      section=g_Sections[sectionname]
      for wddkey in section['wdd'].keys():
         wdd=section['wdd'][wddkey]
         if wdd==WD:
            if File.dir:
               if IN_CREATE & Mask or IN_MOVED_TO & Mask:
                  NewWDD=dict(g_WatchManager.add_watch(PathName,g_WatchFlags,proc_fun=Event_WatchDir,rec=True,auto_add=False))
                  for newpath in NewWDD.keys():
                     section['wdd'][newpath]=NewWDD[newpath]
                  return
               elif IN_MOVED_FROM & Mask or IN_DELETE & Mask:
                  del section['wdd'][PathName]
                  g_WatchManager.rm_watch(PathName,rec=True)
                  return
               else:
                  print("Event_WatchDir: Something went wrong")
            elif File.name.endswith(".torrent"):
               if IN_CLOSE_WRITE & Mask or IN_MOVED_TO & Mask:
                  #print("debug9: WatchDir: %s"%(Mask))
                  if g_PathName != PathName:
                     g_PathName = PathName
                     sleep(1)
                     return Event_NewTorrent(Torrent(section=sectionname,filename=File.name,pickuppath=File.path))
            return True

def Event_NewTorrent(torrent):
   global g_Scripts
   if 'newtorrent' in g_Scripts:
      #print("debug8: NewTorrent: %s"%(torrent.filename))
      if not RunScript(torrent,g_Scripts['newtorrent']):
         return Event_AddFailure(torrent)
      else:
         return Event_AddSuccess(torrent)   

def Event_AddSuccess(torrent):
   global g_Torrents,g_Scripts,g_Threads
   g_Torrents[torrent.uniqueid]=torrent
   if 'addsuccess' in g_Scripts:
      if not RunScript(torrent,g_Scripts['addsuccess']):
         del g_Torrents[torrent.uniqueid]
         return False
   torrent.frozen=False
   mythread=ThreadManager()
   mythread.type=torrent
   mythread.start()
   g_Threads.append(mythread)
   return True

def Event_AddFailure(torrent):
   torrent.frozen=True
   global g_Scripts
   if 'addfailure' in g_Scripts:
      RunScript(torrent,g_Scripts['addfailure'])
   return False

def Event_FileComplete(torrent,File):
   global g_Scripts
   if 'filecomplete' in g_Scripts:
      return RunScript(torrent,g_Scripts['filecomplete'],File=File)
   return True

def Event_TorrentComplete(torrent):
   global g_Scripts
   if 'torrentcomplete' in g_Scripts:
      return RunScript(torrent,g_Scripts['torrentcomplete'])
   return True

def Event_RatioReached(torrent):
   global g_Scripts
   if 'ratioreached' in g_Scripts:
      return RunScript(torrent,g_Scripts['ratioreached'])
   return True

def Event_TorrentFinished(torrent):
   if 'torrentfinished' in g_Scripts:
      if not RunScript(torrent,g_Scripts['torrentfinished']):
         return False
   Event_RemoveTorrent(torrent)

def Event_RemoveTorrent(torrent):
   global g_Torrents,g_Scripts
   torrent.frozen=True
   if 'removetorrent' in g_Scripts:
      RunScript(torrent,g_Scripts['removetorrent'])
   del g_Torrents[torrent.uniqueid]
   return False

def StuffString(torrent,origstring,File=None,Path=None):
   newstring=deepcopy(origstring)
   location1=newstring.find("$sanitize(")
   if location1!=-1:
      location2=newstring[location1+10:].find(')')
      if location2==-1:
         print("StuffString error")
         return origstring
      else:
#         nesttest=newstring[location1+10:].find('(')
#         if nesttest!=-1:
#            if nesttest<location2:
#               location2=newstring[location1+10+nesttest:].find(')')+nesttest
#               print("StuffString error nesttest")
#               return origstring
         newstring="%s%s%s"%(
            newstring[:location1],
            ''.join([c for c in StuffString(torrent,newstring[location1+10:location1+10+location2],Path=Path,File=File)
            if c in "-_.()[],'*&+=%% 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"])
            ,newstring[location1+11+location2:]
         )
   location1=newstring.find("$lowercase(")
   if location1!=-1:
      location2=newstring[location1+11:].find(')')
      if location2==-1:
         print("StuffString error")
         return origstring
      else:
#         nesttest=newstring[location1+11:].find('(')
#         if nesttest!=-1:
#            if nesttest<location2:
#               print("StuffString error nesttest")
#               return origstring
         newstring="%s%s%s"%(
            newstring[:location1],
            StuffString(torrent,newstring[location1+11:location1+11+location2],Path=Path,File=File).lower(),
            newstring[location1+12+location2:]
         )
   location1=newstring.find("[:")
   while location1!=-1:
      location2=newstring[location1:].find(":]")
      if location2==-1:
         print("StuffString error. No closing bracket :]")
         break
      else:
         varname=newstring[location1+2:location1+location2]
         vardata=False
         if varname=="uniqueid":
            vardata=torrent.uniqueid
         elif varname=="name":
            vardata=torrent.name
         elif varname=="pickuppath" and torrent.pickuppath:
            vardata=torrent.pickuppath
         elif varname=="torrentfilename" and torrent.filename:
            vardata=torrent.filename
         elif varname=="section":
            vardata=torrent.section
         elif varname=="linkto" and File:
            fDict=FindFileInDict(torrent.files,File)
            if fDict:
               if 'link' in fDict:
                  vardata=fDict['link']
         elif varname=="file" and File:
            vardata=File
         elif varname=="path" and Path:
            vardata=Path
         elif varname in torrent.variables:
            vardata=torrent.variables[varname]
         else:
            print("StuffString error: No variable named %s"%(varname))
         newstring="%s%s%s"%(newstring[:location1],vardata,newstring[location1+2+location2:])
      location1=newstring.find("[:")
      
   if newstring!=origstring:
      return StuffString(torrent,newstring,Path,File)
   return newstring

def MatchStrings(torrent,StrList,File):
   if len(StrList)!=2:
      print("MatchStrings: Error parsing input (%s)"%(StrList))
   Str1=StuffString(torrent,StrList[0],File)
   Str2=StuffString(torrent,StrList[1],File)
   if Str1==Str2:
      return True
   return False

def RunScript(torrent,script,File=False):
   script.append('')
   success=-1
   retval=True
   for dostuff in script:
      if dostuff:
         docontinue=False
         if dostuff.startswith("if "):
            dostuff=dostuff.split(" ",1)[1]
            if dostuff.startswith("success "):
               if success==True:
                  if not RunScript(torrent,split(dostuff,1)[1:],File):
                     return False
               continue
            elif dostuff.startswith("failure "):
               if success==False:
                  if not RunScript(torrent,split(dostuff,1)[1:],File):
                     return False
               continue
            elif dostuff.startswith("match "):
               Str1,Str2=split(dostuff,2)[1:3]
               if StuffString(torrent,Str1,File)==StuffString(torrent,Str2,File):
                  if not RunScript(torrent,split(dostuff,2)[3:],File):
                     return False
               continue
            elif dostuff.startswith("set "):
               if dostuff.split(" ",2)[1] in torrent.variables:
                  if not RunScript(torrent,split(dostuff,1)[2:],File):
                     return False
               continue
            elif dostuff.startswith("isfile "):
               if path.isfile(StuffString(torrent,dostuff.split(" ",2)[1],File=File)):
                  if not RunScript(torrent,split(dostuff,1)[2:],File):
                     return False
               continue
            else:
               print("RunScript: Error in if-logic. Script:\n%s\nEnd of Script"%(script))
               continue
         if dostuff=="reseterror":
            success=-1
         elif dostuff=="addtorrent":
            success=AddTorrent(torrent)
         elif dostuff=="starttorrent":
            success=False
            if 'transmission' in torrent.variables:
               success=TransmissionRpc('torrent-start',{'ids': [torrent.uniqueid]},torrent)
         elif dostuff=="removeanddelete":
            if 'transmission' in torrent.variables:
               TransmissionRpc('torrent-remove',{'ids': [torrent.uniqueid], 'delete-local-data': True},torrent)
            retval=Event_RemoveTorrent(torrent)
         elif dostuff=="printtorrentinfo":
            torrent.info()
         elif dostuff=="linkfile":
            success=LinkFile(torrent,File)
         elif dostuff=="fail":
            return False
         elif dostuff=="setwantedfiles":
            success=ApplyFilter(torrent)
         elif dostuff.startswith("set "):
            key,value=dostuff.split(" ",2)[1:3]
            torrent.variables[key]=StuffString(torrent,value,File)
         elif dostuff.startswith("movedownloaddir "):
            torrent.variables['downloaddir']=StuffString(torrent,dostuff.split(' ',1)[1])
            success=False
            if 'transmission' in torrent.variables:
               success=TransmissionRpc('torrent-set-location',{'ids': torrent.uniqueid, 'location': torrent.variables['downloaddir'], 'move': True},torrent)
         elif dostuff.startswith("maxratio "):
            success=False
            torrent.variables['maxratio']=dostuff.split(" ",1)[1]
            if 'transmission' in torrent.variables:
               success=TransmissionRpc('torrent-set',{'ids': [torrent.uniqueid], 'seedRatioLimit': torrent.variables['maxratio']},torrent)
         elif dostuff.startswith("umask "):
            umask(int(dostuff.split(' ',1)[1],8))
         elif dostuff.startswith("print "):
            print("%s"%(StuffString(torrent,dostuff[6:],File=File)))
         elif dostuff.startswith("rmdir "):
            success=True
            pathname=StuffString(torrent,dostuff[6:],File=File)
            if path.isdir(pathname):
               try:
                  rmtree(pathname)
               except OSError as e:
                  print("rmdir error(%s)"%(e))
                  success=False
         elif dostuff.startswith("mkdir "):
            success=True
            pathname=StuffString(torrent,dostuff[6:],File=File)
            if not path.isdir(pathname):
               try:
                  makedirs(pathname)
               except OSError as e:
                  print("mkdir error(%s)"%(e))
                  success=False
         elif dostuff.startswith("remove "):
            pathname=StuffString(torrent,dostuff[7:],File=File)
            success=True
            if path.isfile(pathname):
               try:
                  remove(pathname)
               except OSError as e:
                  print("remove error(%s)"%(e))
                  success=False
         elif dostuff.startswith("linkto "):
            success=SetLinkTo(torrent,dostuff.split(" ",1)[1],File)
         elif dostuff.startswith("filterfiles "):
            FilterKey,Filter=dostuff.split(' ',2)[1:3]
            success=FilterFiles(torrent,StuffString(torrent,Filter),FilterKey)
         elif dostuff.startswith("runscript "):
            if not RunScript(torrent,g_Scripts[dostuff.split(" ",1)[1]],File):
               return False
         else:
            print("RunScript: Unknown action: (%s)"%(dostuff))
   return retval

def ApplyFilter(torrent):
   success=False
   if 'transmission' in torrent.variables:
      UnwantedFiles=[]
      for Num,ListItem in enumerate(torrent.files):
         for FileName in ListItem.keys():
            if 'download' not in ListItem[FileName]:
               UnwantedFiles.append(Num)
      if UnwantedFiles!=[]: #Fixme! Set wanted files too!
         success=TransmissionRpc('torrent-set',{'ids': [torrent.uniqueid], 'files-unwanted': UnwantedFiles},torrent)
      else:
         return True
   return success

def SetLinkTo(torrent,LinkTo,File=None):
   if File:
      fDict=FindFileInDict(torrent.files,File)
      if 'link' in fDict:
         head,tail=path.split(File)
         fDict['link']=StuffString(torrent,LinkTo,File=tail,Path=head)
         if fDict['link']!=False:
            return True
         del fDict['link']
   return False

def FindFileInDict(files,FileName):
   for item in files:
      for bleh in item.keys():
         if bleh==FileName:
            return item[bleh]
   return False

def Match(FileName,Filter):
   for FilterType in Filter:
      if fnmatch(FileName.lower(),FilterType.lower()):
         return True
   return False

def FilterFiles(torrent,Filter,FilterKey):
   files=[]
   for ListItem in torrent.files:
      for FileName in ListItem.keys():
         files.append(FileName)
   SelectedFiles=[True for f in files]
   try:
      Filter=split(Filter)
   except ValueError as e:
      print("FilterFiles error: %s"%(e))
      return False
   for FilterOpt in Filter:
      try:
         SplitOpt=split(FilterOpt)
      except ValueError as e:
         print("FilterFiles error: %s"%(e))
         return False
      Item=SplitOpt.pop(0)
      if Item=="deny":
         Value=False
      elif Item=="allow":
         Value=True
      else:
         print("FilterFiles error: Expected deny|allow")
         return False
      Dirs=True
      Files=True
      Item=SplitOpt.pop(0)
      if Item=="dirs":
         Files=False
      elif Item=="files":
         Dirs=False
      elif Item!="any":
         print("FilterFiles error: Expected dirs|files|any")
         return False
      for fnum, File in enumerate(files):
         if Files!=Dirs:
            CompareStr=File.rsplit('/',1)
            if len(CompareStr)==2:
               CompareStr.pop(0)
            CompareStr=CompareStr[0]
         else:
            CompareStr=File
         if Match(CompareStr,SplitOpt):
            #print("%s == %s"%(CompareStr[0],SplitOpt))
            #print("%s:%s"%(Value,files[fnum]))
            SelectedFiles[fnum]=Value
   for Num,Dict in enumerate(torrent.files):
      for FileName in Dict.keys():
         if SelectedFiles[Num]==True:
            Dict[FileName][FilterKey]=SelectedFiles[Num]
         elif SelectedFiles[Num]==False and FilterKey in Dict[FileName]:
            del Dict[FileName][FilterKey]
   return True

def LinkFile(torrent,FileID):
   if not FileID:
      print("LinkFile error: No file")
      return False
   fDict=FindFileInDict(torrent.files,FileID)
   if 'link' not in fDict:
      return -1 #Not success, but it is not failure either
   if fDict['link']==True:
      print("LinkFile: Use linkto first!")
      return False
   LinkFrom='%s/%s'%(torrent.variables['downloaddir'],FileID)
   if not path.isfile(LinkFrom):
      print("LinkFile: %s does not exist"%(LinkFrom))
      return False
   head,tail=path.split(FileID)
   LinkTo=StuffString(torrent,fDict['link'],File=tail,Path=head)
   head,tail=path.split(LinkTo)
   if not path.isdir(head) and tail:
      try:
         makedirs(head)
      except OSError as e:
         print("LinkFile error: %s"%(e))
         return False
   if path.isfile(LinkTo):
      if stat(LinkFrom).st_ino!=stat(LinkTo).st_ino:
         print("LinkFile error: file exists but not same inode")
         return False
   elif tail:
      try:
         link(LinkFrom,LinkTo)
      except OSError as e:
         print("LinkFile error: %s"%(e))
         return False
   else:
      print("LinkFile error:?")
      return False
   global g_Scripts
   retval=True
   if 'linkfile' in g_Scripts:
      retval=RunScript(torrent,g_Scripts['linkfile'],FileID)
   del fDict['link']
   return retval

def LinkFiles(torrent):
   for File in torrent.files:
      for Key in File.keys():
         try:
            if File[Key]['link']!=True:
               if LinkFile(torrent,Key)==False:
                  return False
         except KeyError:
            continue
   return True

def main():
   global g_Sections,g_Scripts,g_Pickle,g_Torrents,g_Threads,g_WatchManager,g_WatchFlags
   
   if not ReadConfig("torrentmanager.conf"):
      return False

   if path.isfile(g_Pickle):
      with open(g_Pickle,mode='rb') as f:
         g_Torrents=pickleload(f)
         for torrentid in g_Torrents.keys():
            torrent=g_Torrents[torrentid]
            if not type(torrent)==type(Torrent()):
               print("Bad pickle!")
               return False
            print("Loaded %s"%(torrent.name))
            torrent.frozen=False
            mythread=ThreadManager()
            mythread.type=torrent
            mythread.start()
            g_Threads.append(mythread)
            del mythread

   WatchSections=[]
   g_WatchManager = WatchManager()
   for sectionname in g_Sections:
      Section=g_Sections[sectionname]
      if 'watchdir' in Section:
         watchdir=Section['watchdir']
         if not path.isdir(watchdir):
            print("Error: Watchdir does not exist: %s"%(watchdir))
            return
         Section['wdd'] = dict(g_WatchManager.add_watch(Section['watchdir'],IN_CLOSE_WRITE|IN_CREATE|IN_MOVED_FROM|IN_MOVED_TO|IN_DELETE,proc_fun=Event_WatchDir,rec=True,auto_add=False))
         WatchSections.append(sectionname)
   if WatchSections!=[]:
      WatchDirThread=ThreadManager()
      WatchDirThread.type=Notifier(g_WatchManager)
      WatchDirThread.start()
      g_Threads.append(WatchDirThread)
      del WatchDirThread

   try:
      while len([t for t in g_Threads if t is not None and t.isAlive()])>0:
         try:
            sleep(15)
         except KeyboardInterrupt:
            for t in g_Threads:
               t.Active=False
            print("Deactivating %s threads"%(len([t for t in g_Threads if t is not None and t.isAlive()])))
            sleep(5)
   except:
      with open(g_Pickle,mode='wb') as f:
         pickledump(g_Torrents,f)
      raise
   else:
      with open(g_Pickle,mode='wb') as f:
         pickledump(g_Torrents,f)

def TransmissionRpc(method,arguments,torrent,Retry=True):
   try:
      try:
         try:
            try:
               Req = Request('http://%s/transmission/rpc'%(torrent.variables['transmission']),jsondumps({'method': method,'arguments': arguments}).encode('utf8'),torrent.variables['transmissionrpcheaders'])
            except KeyError:
               torrent.variables['transmissionrpcheaders']={}
               return TransmissionRpc(method,arguments,torrent)
         except TypeError:
            return TransmissionRpc(method,arguments,torrent)
         try:
            Response=urlopen(Req)
         except urlerror.HTTPError as e:
            if e.code == 409:
               torrent.variables['transmissionrpcheaders']['x-transmission-session-id']=e.headers['x-transmission-session-id']
               if Retry:
                  return TransmissionRpc(method,arguments,torrent,False)
            print(e)
            raise
      except urlerror.URLError as e:
         print("TransmissionRpc:",e.reason,": Is transmission-daemon running?")
         return False
   except:
      print("TransmissionRpc: Unknown error: Is transmission-daemon running?")
      raise
   try:
      Reply=jsonloads(Response.read().decode('utf-8'))
   except TypeError as e:
      print("TransmissionRpc: TypeError:",e,"\n",Reply)
      raise
   if 'result' not in Reply:
      print("TransmissionRpc: Unknown error:",Reply)
      return Reply
   if Reply['result'] == 'success':
      if method=='torrent-get' or method=='torrent-add':
         return Reply
      else:
         try:
            if Reply['arguments']=={}:
               return True
         except Keyerror:
            pass
         print("TransmissionRpc: Success: %s"%(Reply))
         return True
   elif Reply['result'] == "duplicate torrent":
      print("Torrent already added!")
      #Add errorcatching!
      return False
   else:
      print("TransmissionRpc: Weird error:",Reply)
   return False

def TransmissionAddTorrent(torrent):
   if not torrent.metainfo:
      if not torrent.pickuppath or not torrent.filename:
         print("AddTorrent error: Need metainfo or a torrentfile")
         return (False,False)
      try:
          with open('%s/%s'%(torrent.pickuppath,torrent.filename), 'rb') as f:
             torrent.metainfo = b64encode(f.read()).decode('utf-8')
      except IOError as e:
         print("debug5: open: %s"%(e))
         return (False,False)
   Reply=TransmissionRpc('torrent-add',{'metainfo': torrent.metainfo,'paused': False},torrent)
   
   if not Reply:
      #print("debug3: reply:%s"%(Reply))
      return (False,False)
   
   return (Reply['arguments']['torrent-added']['name'],Reply['arguments']['torrent-added']['hashString'])

def TransmissionGetFiles(torrent):
   Reply=TransmissionRpc('torrent-get',{'ids': torrent.uniqueid, 'fields': ['files']},torrent)
   if not Reply:
      return False
   try:
      return [{File['name']:{'bytesCompleted':File['bytesCompleted'],'bytes':File['length']}} for File in Reply['arguments']['torrents'][0]['files']]
   except IndexError as e:
      print("TransmissionGetFiles: Error getting files:(%s) Removing"%(e))
      return Event_RemoveTorrent(torrent)

def AddTorrent(torrent):
   torrent.files=torrent.name=torrent.uniqueid=False
   if 'transmission' in torrent.variables:
      torrent.name,torrent.uniqueid=TransmissionAddTorrent(torrent)
      if torrent.name!=False and torrent.uniqueid!=False:
         #print("debug4:name=%s uid=%s"%(torrent.name,torrent.uniqueid))
         torrent.files=TransmissionGetFiles(torrent)
      
   if not torrent.files or not torrent.name or not torrent.uniqueid:
      #print("debug1\n%s\n%s\n%s"%(torrent.files,torrent.name,torrent.uniqueid))
      return False
   for tItem in torrent.files:
      for tName in tItem.keys():
         tItem[tName]['download']=True
   return True

def PrintDict(d,skiplist=[],indentlevel=0):
   for itemname in d.keys():
      if itemname in skiplist:
         continue
      item=d[itemname]
      if type(item) is dict:
         print("%s%s"%('\t'*indentlevel,itemname))
         PrintDict(item,skiplist,indentlevel+1)
      elif type(item) is list:
         print("%s%s"%('\t'*indentlevel,itemname))
         for i in item:
            if type(i) is dict:
               print("%s%s"%('\t'*(indentlevel+1),i))
               PrintDict(i,skiplist,indentlevel+1)
            else:
               print("%s%s"%('\t'*(indentlevel+1),i))
      else:
         print("%s%s = %s"%('\t'*(indentlevel),itemname,item))

if __name__ == "__main__":
   main()
